今天先來針對第 3 點改進,我們用到很多 switch case,回顧一下狀態機的定義,一個狀態機描述一個實體、對象,根據這點,我們能不能據此做點抽象化呢?既然說狀態機有個對象,如果我們嘗試著直接用 object 來描述看看呢?
首先一樣要有個初始狀態
const machineDef = {
  initialState: "站姿、靜止",  // 初始狀態
};
接著還有狀態機底下的所有狀態
const machineDef = {
  initialState: "站姿、靜止",  // 初始狀態
  // 所有狀態
  states: {
    "站姿、靜止": ?,
    "站姿、移動": ?,
    "跳躍中": ?,
    "俯臥、靜止": ?,
    "俯臥、移動": ?,
  },
};
接著狀態之後該放入什麼東西呢?我們知道至少還有個轉移,nextState = transition(previusState, event),轉移是需要狀態、事件一起作為輸入。
所以上面的 ? 是否可以放入一個 mapping 來存放 event 跟 nextState 的關係。
const machineDef = {
  initialState: "...",  // 初始狀態
  // 所有狀態
  states: {
    "previusState1": mapping1,
    "previusState2": mapping2,
    "previusState3": mapping3,
  },
};
一般 mapping 的實作常見可以使用 function (如果放在物件的會就是 method),然後該 function 裡面可以再用 switch/case 或 if/else 實作,但...回頭來看今天的初衷,就是要避免 switch/case 。
所幸我們想到,mapping 也可以透過 object literal 替代
所以我們就決定採用 object literal 來製作 event 跟 nextState 的對應關係(mapping)吧!
「事件」這兩個字,讓我們回想 JavaScript 如何處理、對待事件,以按鈕 button 為例,<button onclick="">點我啊~</button> ,我們會對處於被動、等待、監聽的狀態時,通常會使用 on 這個前綴字 (如同 onClick, onSubmit, onFocus...)
那就嘗試在每個 state 下,建立一個名為 on 的 object literal ,裡面裝有所有的 event 跟 nextState 的關係(即 transition mapping),對應關係的 key / value 怎麼配? previusState 要配 event ,既然前面已經有 previusState1 再加上我們又使用 on 這個前綴字,想必這個 mapping 的 key 必定是使用 event ,而 value 就是剩下來的 nextState
我們知道
const machineDef = {
  initialState: "...",  // 初始狀態
  // 所有狀態
  states: {
    "previusState1": on:{ 
      "event1": "nextState1",
      "event2": "nextState2",
    },
  },
};
接著要回來繼續實作了,為了簡單看到 previusState, event 跟 nextState ,彼此之間的關係,我們再度呼叫 State Diagram 出來~~~

const machineDef = {
  initialState: "站姿、靜止",
  states: {
    "站姿、靜止": {
      on: {
        跳躍: "跳躍中",
        開始移動: "站姿、移動",
        臥倒: "俯臥、靜止",
      },
    },
    "站姿、移動": {
      on: {
        停止移動: "站姿、靜止",
      },
    },
    跳躍中: {
      on: {
        降落: "站姿、靜止",
      },
    },
    "俯臥、靜止": {
      on: {
        起身: "站姿、靜止",
        開始移動: "俯臥、移動",
      },
    },
    "俯臥、移動": {
      on: {
        停止移動: "俯臥、靜止",
      },
    },
  },
};
如此可讀性是不是看起來比較提升呢?也大幅減少程式碼行數,省掉不必要的 switch / case
那今天針對第三點做了改進
3. 用雙層 switch case 好像比較不好讀,感覺也不太好重複使用
這個 object 看起來就像是 state Machine 的定義一般,那我們可以怎麼操縱這個物件呢?
關於第二點
2. 當 state 跟 event 都是直接使用字串,很容易打錯、出現 bug ,所以想要用常數保護起來
我們可以把 object 的 key 另外再用 const 儲存成常數,或是使用 TypeScript 的 enum
剩下第一點,還有第三點當中的重複使用(這裡還沒多做解釋)
1. State Machine 不完備,沒有一個變數能幫我記憶當下的狀態是什麼,我在使用 transition 時還要另外用一堆變數來儲存 state ,感覺不好維護、也不好維持程式碼的連貫性
我們可以製作一個 function 然後將 machineDef 這個物件 作為 input,明天就來跟大家分享怎麼建立這個 function!
假如有更多種狀態和動作的話,machineDef 可能就會變得非常龐大,有什麼方法可以拆分 machineDef 的嗎?
不好意思這麼晚回覆,我自己目前也在研究的階段
以現階段我可以提出的一些觀點如下:
狀態機開發也是可以有 Atom 的概念,像我前面舉 RPG 的例子,我的遊戲「角色」,是只有移動模組,還可以另外創造攻擊模組、防禦模組這類的狀態機,一起組合成這個遊戲角色整體的狀態。
這個相關的概念也會在幾天後的篇幅被介紹到
Ken Chen謝謝回覆!期待後續的介紹 :)